En el marco del Primer Desafío Internacional de la Red Latinoamericana de Ciencia de Datos, este análisis tiene como propósito abordar un problema práctico del ámbito logístico. El proyecto promueve la colaboración entre estudiantes de diversas universidades latinoamericanas, fomentando el trabajo en equipo y la toma de decisiones basadas en datos reales.
El objeto de estudio es un conjunto de datos proporcionado por iFlow, una empresa argentina especializada en logística integral, con operaciones tanto nacionales como internacionales dentro del MERCOSUR. iFlow se dedica a la gestión y co-gerencia de cadenas de abastecimiento para sus clientes, buscando optimizar procesos y mejorar la eficiencia operativa.
Este análisis tiene como objetivo:
Comprender y describir las principales características del conjunto de datos, que incluye registros de entregas realizadas en un período de tres meses.
Identificar patrones y tendencias que permitan obtener insights relevantes sobre las operaciones de iFlow.
Detectar posibles inconsistencias o errores en la base de datos, propias de un entorno operativo real, para evaluarlas e integrarlas al análisis.
2 Limpieza de datos.
La primera etapa de este análisis consistió en la limpieza de datos. Este proceso de limpieza fue clave para preparar los datos para un análisis exploratorio robusto y la generación de insights confiables sobre la operación logística de iFlow.
En primer lugar realizamos algunos cambios para facilitar el trabajo con los datos.
Tratar Columnas innecesarias
La columna InicioVisitaPlanificado y FinVisitaPlanificado contienen los mismos valores por lo que las unificamos en una nueva columna.
Formatear correctamente las variables
Renombrar las columnas por nombres intuitivos.
Con estos cambios realizados pasamos a modificaciones y arreglos necesarios para un análisis correcto de los datos.
Eliminación de filas duplicadas.
Arreglo de coordenadas faltantes en algunas entregas.
El dato de coordenadas en algunas filas estaba vacio o indicaba “0”. En algunos de estos casos pudimos rellenar estas coordenadas con datos existentes del domicilio (21 filas). En caso de que esto no sea posible las filas fueron eliminadas (19 filas) y no serán tomadas en cuenta para el análisis.
Por último creamos algunas nuevas columnas para distintos análisis. Entre estas algunas columnas para facilitar la interacción con fechas y horarios de entregas.
3 Vista general.
En esta sección ofrecemos una visión superficial de los datos, brindando un panorama inicial que permite familiarizarnos con su estructura y contenido.
Se registraron 27.419 entregas de dos clientes distintos: 16,545 del cliente 20 y 10.874 del cliente 70.
Todas estas entregas fueron realizadas entre el 3 de mayo y 8 de agosto de 2024, con un promedio de 9.005 entregas por mes
Este gráfico de barras muestra la cantidad de entregas realizadas cada mes, separadas por cliente. Las barras están agrupadas por mes y se distinguen por colores para cada cliente.
La cantidad de entregas es constante entre mayo y julio, con algunas variaciones entre los clientes. Esto indica un comportamiento predecible en la demanda mensual. Planificar recursos en función de estos patrones puede mejorar la eficiencia operativa.
En este gráfico se visualiza la cantidad total de entregas realizadas por mes, sin importar el cliente.
n patrón estable en el número de entregas permite predecir la demanda mensual. Las caídas pueden ser analizadas más adelante para identificar feriados o problemas en la operación.
3.1 ¿Cuales son los horarios de entrega?
Este gráfico de calor muestra la cantidad de entregas realizadas según la hora del día y el día de la semana. Cada celda del gráfico representa un cruce entre una hora y un día, con colores más intensos indicando una mayor cantidad de entregas.
Mayor actividad: El miércoles alrededor de las 15:00 es el momento con más entregas. Patrón semanal: Hay menos actividad los fines de semana, especialmente los domingos, donde casi no se registran entregas.
Vemos que el domingo hay solo 3 entregas. Esto podría deberse a un error por lo que las revisamos.
Orden
Localidad
Fecha y hora de entrega
81943
CAPITAL FEDERAL
2024-07-21 23:51:00
100968
CAPITAL FEDERAL
2024-07-21 23:51:00
100968
CAPITAL FEDERAL
2024-07-21 23:51:00
Al revisar estas entregas encontramos que las tres fueron entregadas a las 23:51. Esto podría deberse a un error en la carga de datos o a entregas especiales. Al ser las últimas entregas del día puede deberse a un cierre automático del sistema despues de haber dejado entregas inciadas el día anterior sin marcar su finalización.
Si bien este parece ser un caso puntual existen dos grandes problemas respecto a los horarios y duración de las entregas. Estos problemas son.
Duración de las entregas: Muchas entregas tienen el mismo horario de inicio y fin. Esto puede deberse a errores en la carga de datos o a un sistema de carga poco eficiente.
Entregas consecutivas: Muchas entregas consecutivas fueron cargadas con el horario de la entrega anterior. Esto dificulta la identificación de rutas, estimación de tiempos muertos y duraciones reales entre entregas.
3.2 Errores de carga en horarios de entrega.
Notamos que en las entregas muy cercanas geograficamente, en la misma cuadra, suelen tener el mismo horario de finalización. Esto se puede deber a que los operarios olvidan hacer la carga individual o consideran mas rapido completar ambas entregas antes de registrarlo en el sistema.
Junto con los errores de carga en los horarios de entrega este puede ser un segundo indicador de que el sistema de carga puede ser mejorado para no recolectar datos erroneos en el futuro.
Horarios cargados de forma incorrecta podrian causar:
Mala estimación sobre tiempos muertos.
Dificulta optimizar los procesos de entrega.
Perjudica la proyección de horarios de entregas o ventanas horarias.
Algunas sugerencias e ideas para mejorar esto incluyen:
Mejoras de la interfaz en el sistema de carga para facilitar y fomentar su uso.
Implementación de un sistema de validación de los datos para evitar duplicados.
Desarrollo y uso de hardware específico para la carga de datos.
¿Cuál es el tiempo promedio entre el inicio y fin de las visitas de entrega?
Bien cargados 17142 vs mal cargados 10255 mal. Pero encontramos que son incluso más. Una forma de solucionarlo podría haber sido ordenar las entregas en orden cronologico e intentar estimar la duración real segun el tiempo entre las entregas y la distancia entre ellas pero al intentar esto encontramos que muchas entregas consecutivas arrastran errores. Dificultando la identificación de rutas, estimación de tiempos muertos y duraciones reales entre entregas.
Como referencia, el día 2024-06-27 16:06:00 hay 23 entregas graficadas el mismo día.
Con 12799 entregas con horarios mal registrados representando un 46.67% los datos disponibles cargados de forma incorrecta.
Al desconfiar del 46.67% de los datos es dificil y poco preciso cualquier tipo de analisis sobre la eficiencia operativa de las entregas. Esto puede traer posibles problemas como:
Problema 1
Problema 2
Solucionar esta situacion representa una gran oportunidad y facilitaria exponencialmente el crecimiento de la empresa, precision de las ventanas de entrega, identificación de cuellos de botella reales y a final de cuentas la toma de decisiones operativas.
“Lo que no se mide, no se puede mejorar.” - Peter F. Drucker
A continuación listamos algunos posibles motivos y oportunidades para corregir la situación.
Manual de uso y capacitación sobre el sistema UNIGIS
Capacitar mejor al personal con mayor cantidad de recursos, claridad en los instructivos y videos demostrando el uso correcto del sistema videos demostrando el uso correcto del sistema mejoraría la precision de la carga de los datos en el futuro.
Creación de una interfaz personalizada para Unigis.
El error de carga de horarios iguales en entregas consecutivas puede deberse principalmente a la dificultad de uso del sistema o poca practicidad del mismo por la que los transportistas podrían saltear los pasos del instructivo.
Si migrar a un nuevo sistema más moderno es una alternativa muy costosa podrían considerar hacer una inversión en desarrollo frontend para, utilizando la API del sistema actual, puedan tener una interfaz más amena a los transportistas.
Referencia del Uso de la API cloud de Unigis
El desarrollo de una interfaz personalizada para interactuar con su sistema actual podría ser una inversión considerable pero economica contrastando con la posibilidad de un desarrollo personalizado o la migración a un nuevo sistema.
Algunas consideraciones:
Inversión en equipo e investigación UX para asegurar el uso intuitivo de los transportistas. Es importante entender como es el uso del sistema en la practica.
Migrar a un sistema más moderno o diseñar uno a medida para sus necesidades.
Puede ser la opción mas costosa.
3.3 Información de bultos y peso.
¿Cuál es el promedio de bultos, peso y unidades entregadas por pedido?
En promedio cada entrega tiene:
28.40 Unidades
5.75 Bultos
Un peso de 41.15kg
Un peso promedio elevado por pedido puede indicar productos de gran volumen o de alto consumo. Esto tiene implicaciones en los costos de transporte y las rutas de entrega.
¿Cuál es la distribución de entregas por localidad o región?
Como podemos ver este mapa de entregas de la ciudad de Buenos Aires muestra visualmente la dispersión de las entregas en la ciudad. La variedad de colores permite ver qué clientes se agrupan en ciertas áreas. Si ciertos clientes concentran entregas en una región específica, eso puede indicar una demanda localizada o preferencia regional de ciertos productos.
En este mapa las áreas con mayor densidad de entregas resaltan visualmente. Estas zonas de alta actividad pueden representar zonas comerciales o residenciales clave. Esto es útil para optimizar las rutas de entrega y asignar más recursos a zonas con alta demanda.
En base a este mapa se permite ver cómo se distribuyen las entregas a nivel de barrio. Los barrios con mayor intensidad de color son aquellos con más entregas, lo que indica la importancia de estos barrios en el volumen de pedidos.
Similar al mapa por barrio, pero agrupando entregas por comunas. Las comunas que presentan más entregas destacan como puntos de interés para analizar el impacto logístico y la demanda concentrada.
Los puntos en el mapa de Buenos Aires representan ubicaciones de entregas, y las áreas con mayor densidad de entregas aparecen con colores más intensos, permitiendo identificar visualmente las zonas con más actividad de entregas.
`summarise()` has grouped output by 'latitud'. You can override using the
`.groups` argument.
3.4.1 PENDIENTE Centro de distribución en Mendoza.
mostrar grafico, imagen de google maps y explicación.
¿Cuántas entregas se hicieron fuera del tiempo esperado o planificado?
`summarise()` has grouped output by 'mes'. You can override using the `.groups`
argument.
Este gráfico permite la visualización de la cantidad de entregas por mes, clasificadas en “Tarde” y “Temprano o a Tiempo”.
¿Qué clientes generan más volumen de entregas y cuáles presentan más problemas o irregularidades?
Este mapa permite la visualización de un mapa interactivo que muestra las entregas realizadas, clasificado por cliente, en Buenos Aires.
3.5 Conclusiones
4 Segmentación y patrones en entregas.
4.1 PENDIENTE Volumen y Distribución de Entregas
¿Cuántas entregas corresponden a cada cliente?
Este gráfico de barras permite la visualización de la cantidad total de entregas realizadas por cada cliente, utilizando barras que muestran el número de entregas para cada uno
Identificar entregas recurrentes a domicilios,
`summarise()` has grouped output by 'cliente'. You can override using the
`.groups` argument.
Este gráfico de lineas permite la visualización de la cantidad de entregas realizadas por cliente a lo largo de los días de la semana, excluyendo el domingo. Utiliza líneas para representar la evolución del número de entregas para cada cliente, con puntos destacados en cada día.
Los domicilios reciben entregas de un único cliente o solo 20 o 70?
Este gráfico permite la visualización de la distribución de la cantidad de entregas por domicilio, mostrando un histograma que representa la frecuencia de entregas para diferentes valores.
Este gráfico de barra permite la visualización de la cantidad de domicilios que han recibido más de una entrega. Utiliza barras para representar el número total de domicilios, distinguiendo entre aquellos que tienen mas de una entrega y los que no.
¿Qué porcentaje de entregas se concentra en las localidades más activas?
Este gráfico permite la visualización de cuántas entregas se han realizado en cada cluster, mostrando la cantidad de entregas en un gráfico de barras. Las barras están ordenadas de mayor a menor, lo que facilita ver rápidamente en qué clusters hay más actividad
Este mapa permite la visualización de las áreas cubiertas por cada cluster en Buenos Aires, utilizando envolventes convexos para representar la extensión de cada grupo de entregas. En él, se muestran los puntos de datos de las entregas, coloreados según su cluster, lo que facilita identificar visualmente la distribución geográfica.
`summarise()` has grouped output by 'cluster'. You can override using the
`.groups` argument.
Este grafico de barras muestra la cantidad de entregas por dia de la semana y por cluster. El gráfico permite comparar fácilmente la cantidad de entregas realizadas en cada día, desglosadas por los diferentes clusters identificados en Buenos Aires.
`summarise()` has grouped output by 'cluster'. You can override using the
`.groups` argument.
Este gráfico de barras permite visualizar la cantidad de entregas por cluster, mostrando en el eje X los diferentes clusters y en el eje Y el número total de entregas, con cada barra coloreada según el cliente para facilitar la comparación de la distribución de entregas en Buenos Aires.
4.2 PENDIENTE Eficiencia y Rendimiento Operativo
¿Cuál es el tiempo promedio de entrega por cliente y por localidad?
¿Qué zonas presentan mayores retrasos o entregas fuera de tiempo?
Identifica áreas con posibles cuellos de botella logísticos.
¿Se detectan diferencias significativas en los tiempos de entrega según la cantidad de bultos o peso?
5 Análisis de series temporales
Tenemos 4 grandes caidas en las entregas por lo que buscamos identificar el motivo en cada fecha.
Por simplicidad asumimos que se debe a falta de datos en la fecha para comparir el dataset.
Para realizar un pronóstico de demanda para el mes de agosto vamos no vamos a tomar en cuenta estos 4 casos asumiendolos como irrelevantes para el modelo. Limitando los datos hasta el 30 de julio
Con los datos ordenados ahora podemos descomponer la serie temporal en tendencia, estacionalidad y residuales.
Series: train_ts
ARIMA(1,0,1) with non-zero mean
Coefficients:
ar1 ma1 mean
-0.5190 0.7919 380.8660
s.e. 0.2562 0.1729 9.5105
sigma^2 = 3140: log likelihood = -243.59
AIC=495.19 AICc=496.19 BIC=502.41
En los datos proporcionados no contamos con la información necesaria para realizar un análisis específico de las unidades de transporte, ya que sería indispensable disponer de un identificador único para cada vehículo. Sin embargo, incluimos esta sección como prueba de concepto para demostrar el valor que podría generar este tipo de análisis en la operación logística. Disponer de esta información permitiría evaluar aspectos fundamentales de la gestión de la flota, optimización de rutas y eficiencia operativa.
A continuación, presentamos algunas preguntas clave que podrían responderse con un análisis detallado de las unidades de transporte:
6.1Preguntas sobre Desempeño y Utilización de la Flota
¿Cuánto tiempo real dedica cada unidad a entregas versus tiempo muerto (espera, carga, mantenimiento)?
¿Cuáles son los tiempos de ruta promedio por camión y cómo varían según la región?
¿Es necesaria la cantidad actual de camiones, o existe capacidad ociosa que podría aprovecharse?
¿Hay rutas o zonas específicas donde sería más eficiente reducir o ampliar la flota?
¿Qué porcentaje de las unidades completan sus rutas dentro de los tiempos planificados?
6.1.0.1Preguntas sobre Optimización de Rutas y Rendimiento
¿Se podrían consolidar entregas para reducir la cantidad de viajes sin afectar el servicio?
¿Existen unidades con rutas ineficientes que podrían optimizarse con ajustes?
¿Cuál es la relación entre la distancia recorrida y el volumen entregado por unidad?
¿Se podrían reducir tiempos muertos al mejorar la planificación de entregas o las ventanas horarias?
Si bien no contamos con la información completa sobre las unidades de transporte, hemos realizado estimaciones basadas en los datos disponibles y presentamos nuestro análisis como aproximación para obtener insights relevantes.
Ver las sedes de Iflow (que conocemos)
Graficar la primera entrega de cada día. Graficar la primera y segunda entrega de cada día. Esperariamos que estas entregas rodeen cada sede. Entender su comportamiento.
Primera y segunda entrega de cada día
Grafico de orden de las entregas de un día en específico.
Parece que los camiones no mezclan productos del cliente 20 y 70.
Tiempos muertos, identificar trayectorias por distancia.
Errores de carga cuando hay localidades consecutivas.
Grafico de trayectoria en orden para cada cliente. Identificar puntos que demuestran rutas poco eficientes. Cruces de lineas, tiempo perdido.
Graficar tiempo entre entregas por hora del día. (Tiempo muerto)
Ampliar u organizar nuevos centros de distribución. (En base a los datos de estos dos clientes), Segmentación de entregas (global y por cliente) para identificar puntos donde sería ideal. (Centro de zona, cruce de zonas, cluster con más o menor actividad)
6.2 Intentos de identificar transportistas.
A priori y para facilitar el análisis podríamos suponer que las entregas las realizan dos camiones. Uno para cada cliente. Podemos validar que esto es incorrecto al estudiar las entregas de un único cliente.
Vemos que entre la entrega 18 y 19 pasaron menos de 3min
Hay algunos casos donde podemos identificar camiones distintos:
Grandes distancias en poco tiempo.
Cruces de rutas.
Los cruces de trayectorias podrían indicar rutas ineficientes de los repartidores o que se estan siguiendo varios repartidores en la misma ruta. Podemos verlo de forma clara en el siguiente gráfico:
Habiendo identificado estas caracteristicas podríamos utilizar un modelo de agrupamiento para estimar distintos repartidores y sus trayectorias.
Esto tiene un gran valor para:
Identificar rutas ineficientes.
Estimar un tiempo de duración de cada entrega (Y rellenar vacios)
Identificar patrones en los errores de carga de datos (Duración de entregas)
Para la aplicación practica de estos analisis sería vital tener acceso real a los camiones o repartidores responsables de cada entrega pero para el bien de este analisis intentamos aproximar lo más posible a estos datos utilizando modelos estadisticos. Esperamos sirva como prueba de concepto para evaluar el potencial de recolectar estos datos.
Esto nos presenta un problema que podría solucionarse con coloración de grafos.
Construir el grafo de conflictos:
Nodos: Cada nodo representa una entrega individual con su respectiva latitud, longitud, fecha y hora.
Aristas (conflictos): Se dibuja una arista entre dos entregas si es imposible que hayan sido realizadas por el mismo repartidor debido a restricciones de tiempo y distancia.
Criterio de conflicto: Para dos entregas AAA y BBB, se calcula el tiempo mínimo necesario para que un repartidor viaje desde la ubicación de AAA a BBB considerando una velocidad razonable (por ejemplo, la velocidad promedio de un vehículo en esa área).
Si el tiempo transcurrido entre la hora de entrega de AAA y BBB es menor que el tiempo mínimo de viaje calculado, entonces se establece un conflicto entre AAA y BBB.
Algoritmos sugeridos:
Algoritmo Greedy de coloreo: Un enfoque sencillo que asigna colores a los nodos de forma secuencial, utilizando el menor número de colores posible en cada paso.
Heurísticas avanzadas: Si el grafo es grande y complejo, pueden emplearse algoritmos como DSATUR o técnicas metaheurísticas (algoritmos genéticos, recocido simulado) para aproximar una solución cercana al mínimo número de colores.
Algunos resultados obtenidos utilizando el algoritmo Greedy de coloreo con python:
Si bien funciona correctamente para alrededor de 20 entregas a medida que agregamos datos se dificulta el algoritmo y considerando las incosistencias de los datos y errores de carga no sería adecuado estimar usando este algoritmo en su estado actual.
Algunas consideraciones.
Toma en cuenta una velocidad promedio de los camiones arbitraira
Si bien podría ser interesante utilizar un algoritmo para resolver o estimar el problema. Evidentmente la mejor solución es tomar los datos de UNIGIS o un software automatico. De todas formas no se podrá extraer valor real con datos de tiempo erroneos.
Esta estimación nos presenta algunas preguntas sobre factores de las entregas.
Rutas optimizadas.
Gracias al análisis podemos ver que las rutas estan optimizadas pero parecen estar diseñadas para transportistas individuales y no para la flota como grupo. Optimizar las rutas de entregas para toda la flota como un ente es distinto a diseñar la ruta para cada camion entre dos puntos.
Incluir enlace interno para versión del trabajo con el codigo y comentarios.
Incluir el enlace al repositorio de Github
Referencias de estudio sobre Lastmile logistics, ETL in logistics y clustering geoespacial DBSCAN, H3 oreilly
Ejecutar el código
---title: "Caso de estudio | Iflow"date: "`r Sys.Date()`" # Inserta la fecha automáticamenteformat: html: theme: darkly toc: true toc-location: left number-sections: true code-fold: true # Permitir plegado de bloques de código code-tools: true # Mostrar herramientas de copiado en los bloques de código smooth-scroll: true # Habilitar desplazamiento suave al navegar por el índice fig-align: center # Alinear las figuras al centro toc-depth: 3 # Profundidad máxima del índicelang: es # Establecer idioma del documentoeditor: visual # Editor visual habilitado---```{r,warning=FALSE,echo=FALSE, output=FALSE}# Limpieza de datoslibrary(tidyverse)library(dplyr)library(skimr)library(lubridate)# Analisis exploratoriolibrary(DataExplorer)library(inspectdf)library(plotly)library(viridis)# Análisis de series temporaleslibrary(tseries)library(forecast)# Clusteringlibrary(factoextra)# Claves de APIsmapbox_token <- "pk.eyJ1IjoibG9yZW5uem8iLCJhIjoiY20xcHYyd3g2MDk0bTJxb2k4YWZvOHlmcSJ9.r4E2pcTSM89NNHBFSmvKHw"# Cargamos los datosdata <- read_csv("data/raw_iflow_data.csv", show_col_types = FALSE)```# IntroducciónEn el marco del **Primer Desafío Internacional de la Red Latinoamericana de Ciencia de Datos**, este análisis tiene como propósito abordar un problema práctico del ámbito logístico. El proyecto promueve la colaboración entre estudiantes de diversas universidades latinoamericanas, fomentando el trabajo en equipo y la toma de decisiones basadas en datos reales.El **objeto de estudio** es un conjunto de datos proporcionado por **iFlow**, una empresa argentina especializada en logística integral, con operaciones tanto nacionales como internacionales dentro del MERCOSUR. iFlow se dedica a la gestión y co-gerencia de cadenas de abastecimiento para sus clientes, buscando optimizar procesos y mejorar la eficiencia operativa.Este análisis tiene como objetivo:1. **Comprender y describir** las principales características del conjunto de datos, que incluye registros de entregas realizadas en un período de tres meses.2. **Identificar patrones y tendencias** que permitan obtener insights relevantes sobre las operaciones de iFlow.3. **Detectar posibles inconsistencias o errores** en la base de datos, propias de un entorno operativo real, para evaluarlas e integrarlas al análisis.# Limpieza de datos.La primera etapa de este análisis consistió en la **limpieza de datos**. Este proceso de limpieza fue clave para preparar los datos para un análisis exploratorio robusto y la generación de insights confiables sobre la operación logística de iFlow.En primer lugar realizamos algunos cambios para **facilitar el trabajo** con los datos.- Tratar Columnas innecesarias La columna InicioVisitaPlanificado y FinVisitaPlanificado contienen los mismos valores por lo que las unificamos en una nueva columna.```{r,warning=FALSE,echo=FALSE}data <- data %>% # Nueva columna para almacenar el horario planificado mutate(visita_planificada = InicioVisitaPlanificado) %>% # Eliminamos InicioVisitaPlanificado y FinVisitaPlanificado dplyr::select(-InicioVisitaPlanificado, -FinVisitaPlanificado)```- Formatear correctamente las variables```{r,warning=FALSE,echo=FALSE}# Convertir columnas correspondientes a formato de fecha y horadata$InicioVisitaReal <- as.POSIXct(data$InicioVisitaReal, format="%Y-%m-%d %H:%M:%OS")data$FinVisitaReal <- as.POSIXct(data$FinVisitaReal, format="%Y-%m-%d %H:%M:%OS")data$visita_planificada <- as.POSIXct(data$visita_planificada, format="%Y-%m-%d %H:%M:%OS")# Las columnas InicioHorario1, FinHorario1, las pasamos a caracter para categorizarlas facilmente.data$InicioHorario1 <- as.character(data$InicioHorario1)data$FinHorario1 <- as.character(data$FinHorario1)# Pasamos variables categóricas a factores.data$cliente <- as.factor(data$cliente)```- Renombrar las columnas por nombres intuitivos.```{r ,warning=FALSE,echo=FALSE}# Renombrar columnas específicas con dplyrdata <- data %>% rename( id_orden = iddomicilioorden, inicio_horario = InicioHorario1, fin_horario = FinHorario1, bultos = Bultos, peso = Peso, unidades = Unidades, inicio_visita = InicioVisitaReal, fin_visita = FinVisitaReal)# Reorganizar columnas.data <- data %>% dplyr::select(id_orden, cliente, localidad, direccion, latitud, longitud, bultos, unidades, peso, inicio_horario, fin_horario, visita_planificada, inicio_visita, fin_visita)```Con estos cambios realizados pasamos a modificaciones y **arreglos necesarios** para un análisis correcto de los datos.- Eliminación de filas duplicadas.```{r,warning=FALSE,echo=FALSE}data <- data %>% distinct()```- Arreglo de coordenadas faltantes en algunas entregas. El dato de coordenadas en algunas filas estaba vacio o indicaba "0". En algunos de estos casos pudimos rellenar estas coordenadas con datos existentes del domicilio (21 filas). En caso de que esto no sea posible las filas fueron eliminadas (19 filas) y no serán tomadas en cuenta para el análisis.```{r,warning=FALSE,echo=FALSE}# Filtrar las filas donde latitud o longitud son NAcordenadas_vacias <- data %>% filter( is.na(latitud) | is.na(longitud) | latitud == 0 | longitud == 0 )# cordenadas_vacias # dim 43 x 14# Filtrar las observaciones donde id_orden está en cordenadas_vaciasobservaciones_id_orden <- data %>% filter(id_orden %in% cordenadas_vacias$id_orden) %>% group_by(id_orden) %>% summarise(count = n())# Mostrar el resultado# observaciones_id_orden# Contar las apariciones de cada id_orden en cordenadas_vaciasapariciones_cordenadas_vacias <- cordenadas_vacias %>% group_by(id_orden) %>% summarise(na_count = n())# Unir las tablas por id_ordenresultado <- observaciones_id_orden %>% left_join(apariciones_cordenadas_vacias, by = "id_orden") %>% # Si no hay coincidencias en cordenadas_vacias, establecer na_count en 0 mutate(na_count = ifelse(is.na(na_count), 0, na_count)) %>% # Restar las apariciones de cordenadas_vacias del total mutate(count_diff = count - na_count) %>%# Filtrar solo los id_orden donde count_diff es mayor a 0 filter(count_diff > 0)# Mostrar el resultado#resultado``````{r,warning=FALSE,echo=FALSE}# Definir la función que revisa y sobrescribe latitud y longitudreparar_lat_long <- function(dataset, ids) { # Iterar sobre cada id de la lista for (id in ids) { # Filtrar las observaciones válidas de latitud y longitud para este id_orden observaciones_validas <- dataset %>% filter(id_orden == id & !is.na(latitud) & !is.na(longitud) & latitud != 0 & longitud != 0) # Si existen observaciones válidas, tomar la primera ocurrencia if (nrow(observaciones_validas) > 0) { latitud_valida <- observaciones_validas$latitud[1] longitud_valida <- observaciones_validas$longitud[1] # Sobrescribir las observaciones con latitud o longitud nulos o 0 dataset <- dataset %>% mutate( latitud = ifelse(id_orden == id & (is.na(latitud) | latitud == 0), latitud_valida, latitud), longitud = ifelse(id_orden == id & (is.na(longitud) | longitud == 0), longitud_valida, longitud) ) } } # Retornar el dataset reparado return(dataset)}``````{r,warning=FALSE,echo=FALSE}# Ejecutar la función usando los id_orden de la columna resultadoids_a_reparar <- resultado$id_orden# Aplicar la función a raw_datadata <- reparar_lat_long(data, ids_a_reparar)```Por último creamos algunas nuevas columnas para distintos análisis. Entre estas algunas columnas para facilitar la interacción con fechas y horarios de entregas.```{r,warning=FALSE,echo=FALSE, output=FALSE}# Asegurar que los días se generen en españolSys.setlocale("LC_TIME", "es_ES.UTF-8") # Crear la columna 'dia_str' con normalización de caracteresdata <- data %>% mutate( dia = as.integer(format(fin_visita, "%d")), mes = as.integer(format(fin_visita, "%m")), hora = as.integer(format(fin_visita, "%H")), diferencia_minutos = as.numeric( difftime(fin_visita, visita_planificada, units = "mins")), dia_str = tolower(iconv(weekdays(fin_visita, abbreviate = FALSE), to = "UTF-8")), duracion_visita_min = as.numeric( difftime(fin_visita, inicio_visita, units = "mins")), duracion_visita_horas = as.numeric( difftime(fin_visita, inicio_visita, units = "hours")) )# Guardamos los datos limpios# write.csv(x = data, file = "iflow_clean.csv")``````{r,warning=FALSE,echo=FALSE}data <- read.csv("data/clean_iflow_data.csv")```# Vista general.En esta sección ofrecemos una **visión superficial de los datos**, brindando un panorama inicial que permite familiarizarnos con su estructura y contenido.```{r,warning=FALSE,echo=FALSE, output=FALSE}# Cuantas entregas tenemos en total?dim(data)dim(data %>% filter(cliente==20))dim(data %>% filter(cliente==70))```Se registraron 27.419 entregas de dos clientes distintos: 16,545 del cliente 20 y 10.874 del cliente 70.Todas estas entregas fueron realizadas entre el 3 de mayo y 8 de agosto de 2024, con un promedio de 9.005 entregas por mes```{r,warning=FALSE,echo=FALSE, output=FALSE}# Ensure fin_visita is in the correct POSIXct formatdata$fin_visita <- as.POSIXct(data$fin_visita, format = "%Y-%m-%d %H:%M:%OS")# Filter the row with the maximum fin_visitadata %>% filter(fin_visita == min(fin_visita, na.rm = TRUE))data %>% filter(fin_visita == max(fin_visita, na.rm = TRUE))```::: panel-tabset## Entregas por cliente```{r,warning=FALSE,echo=FALSE}# Crear una columna con el primer día del mes correspondientedata <- data %>% mutate(mes = as.Date(floor_date(fin_visita, "month"))) # Asegurar que 'mes' sea Date# Agrupar por mes y contar la cantidad de entregasentregas_por_mes <- data %>% group_by(mes, cliente) %>% summarise(n = n(), .groups = "drop")entregas_por_mes$cliente <- as.factor(entregas_por_mes$cliente)# Crear el gráfico de barrasggplot(entregas_por_mes, aes(x = mes, y = n, fill=cliente)) + geom_bar(stat = "identity", position="dodge") + scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") + labs(title = "Cantidad de Entregas por Mes por cliente", x = "Mes", y = "Número de Entregas") + theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1))```Este gráfico de barras muestra la cantidad de entregas realizadas cada mes, separadas por cliente. Las barras están agrupadas por mes y se distinguen por colores para cada cliente.La cantidad de entregas es constante entre mayo y julio, con algunas variaciones entre los clientes. Esto indica un comportamiento predecible en la demanda mensual. Planificar recursos en función de estos patrones puede mejorar la eficiencia operativa.## Entregas por mes```{r,warning=FALSE,echo=FALSE}# Crear una columna con el primer día del mes correspondientedata <- data %>% mutate(mes = as.Date(floor_date(fin_visita, "month"))) # Asegurar que 'mes' sea Date# Agrupar por mes y contar la cantidad de entregasentregas_por_mes <- data %>% group_by(mes) %>% summarise(n = n(), .groups = "drop")# Crear el gráfico de barrasggplot(entregas_por_mes, aes(x = mes, y = n)) + geom_bar(stat = "identity", fill = "#94C11F") + scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") + labs(title = "Cantidad de Entregas por Mes", x = "Mes", y = "Número de Entregas") + theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1))```En este gráfico se visualiza la cantidad total de entregas realizadas por mes, sin importar el cliente.n patrón estable en el número de entregas permite predecir la demanda mensual. Las caídas pueden ser analizadas más adelante para identificar feriados o problemas en la operación.:::## ¿Cuales son los horarios de entrega? {#horarios-entrega}```{r, warning=FALSE, echo=FALSE}# Agrupar los datos por día y hora para contar ocurrenciasresumen <- data %>% group_by(dia_str, hora) %>% summarise(n = n(), .groups = "drop") %>% ungroup()# Asegurar el orden correcto de los días (Lunes a Domingo)resumen$dia_str <- factor(resumen$dia_str, levels = c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"))resumen <- resumen %>% filter(!is.na(dia_str), dia_str != "domingo")ggplot(resumen, aes(x = hora, y = dia_str, fill = n)) + geom_tile(color = "white") + scale_fill_viridis(option = "C", direction = 1) + labs(title = "Entregas por día y hora", x = "Hora del día", y = "Día de la semana", fill = "Cantidad") + theme_minimal()```Este gráfico de calor muestra la cantidad de entregas realizadas según la hora del día y el día de la semana. Cada celda del gráfico representa un cruce entre una hora y un día, con colores más intensos indicando una mayor cantidad de entregas.Mayor actividad: El miércoles alrededor de las 15:00 es el momento con más entregas. Patrón semanal: Hay menos actividad los fines de semana, especialmente los domingos, donde casi no se registran entregas.```{r,warning=FALSE,echo=FALSE}# Convertir la columna dia_str en un factor ordenadodata <- data %>% mutate(dia_str = factor(dia_str, levels = c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado","domingo")))# Agrupar por el nombre del día y contar las entregasentregas_por_dia <- data %>% group_by(dia_str) %>% summarise(n = n())# Crear el gráfico de barrasggplot(entregas_por_dia, aes(x = dia_str, y = n)) + geom_bar(stat = "identity", fill = "#94C11F") + labs(title = "Cantidad de Entregas por Día de la Semana", x = "Día de la Semana", y = "Número de Entregas") + theme_minimal()```Vemos que el domingo hay solo 3 entregas. Esto podría deberse a un error por lo que las revisamos.```{r,warning=FALSE,echo=FALSE, output=FALSE}data %>% filter(dia_str == "domingo")```| Orden | Localidad | Fecha y hora de entrega ||--------|-----------------|-------------------------|| 81943 | CAPITAL FEDERAL | 2024-07-21 23:51:00 || 100968 | CAPITAL FEDERAL | 2024-07-21 23:51:00 || 100968 | CAPITAL FEDERAL | 2024-07-21 23:51:00 |Al revisar estas entregas encontramos que las tres fueron entregadas a las 23:51. Esto podría deberse a un error en la carga de datos o a entregas especiales. Al ser las últimas entregas del día puede deberse a un cierre automático del sistema despues de haber dejado entregas inciadas el día anterior sin marcar su finalización.Si bien este parece ser un caso puntual existen dos grandes problemas respecto a los horarios y duración de las entregas. Estos problemas son.1. **Duración de las entregas:** Muchas entregas tienen el mismo horario de inicio y fin. Esto puede deberse a errores en la carga de datos o a un sistema de carga poco eficiente.2. **Entregas consecutivas:** Muchas entregas consecutivas fueron cargadas con el horario de la entrega anterior. Esto dificulta la identificación de rutas, estimación de tiempos muertos y duraciones reales entre entregas.## Errores de carga en horarios de entrega.Notamos que en las entregas muy cercanas geograficamente, en la misma cuadra, suelen tener el mismo horario de finalización. Esto se puede deber a que los operarios olvidan hacer la carga individual o consideran mas rapido completar ambas entregas antes de registrarlo en el sistema.Junto con los errores de carga en los horarios de entrega este puede ser un segundo indicador de que el sistema de carga puede ser mejorado para no recolectar datos erroneos en el futuro.Horarios cargados de forma incorrecta podrian causar:1. Mala estimación sobre tiempos muertos.2. Dificulta optimizar los procesos de entrega.3. Perjudica la proyección de horarios de entregas o ventanas horarias.Algunas sugerencias e ideas para mejorar esto incluyen:- Mejoras de la interfaz en el sistema de carga para facilitar y fomentar su uso.- Implementación de un sistema de validación de los datos para evitar duplicados.- Desarrollo y uso de hardware específico para la carga de datos.> ¿Cuál es el tiempo promedio entre el inicio y fin de las visitas de entrega?```{r, warning=FALSE, echo=FALSE}# dim(data %>% filter(inicio_visita != fin_visita)) # 17142# data %>% filter(inicio_visita == fin_visita) # 10255# Calcular los totales y proporcionestotal <- nrow(data) # Total de filasdif_visit <- nrow(data %>% filter(inicio_visita != fin_visita)) # 17142 filas diferentesigual_visit <- nrow(data %>% filter(inicio_visita == fin_visita)) # Filas iguales# Crear un dataframe con los resultadosresumen <- data.frame( Categoria = c("Inicio ≠ Fin", "Inicio = Fin"), Conteo = c(dif_visit, igual_visit))# Calcular el porcentaje para cada categoríaresumen$Porcentaje <- round((resumen$Conteo / total) * 100, 2)# Crear el gráfico de barrasggplot(resumen, aes(x = Categoria, y = Conteo, fill = Categoria)) + geom_bar(stat = "identity", width = 0.6) + geom_text(aes(label = paste0(Porcentaje, "%")), vjust = -0.5, size = 5) + # Etiquetas con porcentaje arriba de las barras labs(title = "Comparación de visitas", x = "Categoría", y = "Cantidad de visitas") + theme_minimal()```Bien cargados 17142 vs mal cargados 10255 mal. Pero encontramos que son incluso más. Una forma de solucionarlo podría haber sido ordenar las entregas en orden cronologico e intentar estimar la duración real segun el tiempo entre las entregas y la distancia entre ellas pero al intentar esto encontramos que muchas entregas consecutivas arrastran errores. Dificultando la identificación de rutas, estimación de tiempos muertos y duraciones reales entre entregas.```{r, warning=FALSE, echo=FALSE, output=FALSE}# Ordenar los datos cronológicamente (asumo que tienes una columna de timestamp)data_ordenada <- data %>% arrange(inicio_visita)# Crear una columna que identifique si el fin_visita es igual al de la fila anteriordata_secuencia <- data_ordenada %>% mutate( consecutivo = (fin_visita == lag(fin_visita, default = first(fin_visita))) )# Crear un identificador para cada grupo consecutivo con el mismo fin_visitadata_secuencia <- data_secuencia %>% mutate( grupo = cumsum(!consecutivo) # Incrementar grupo cuando cambia fin_visita )# Resumir el número de filas en cada grupo consecutivoresumen_secuencias <- data_secuencia %>% group_by(fin_visita, grupo) %>% summarise(cantidad = n(), .groups = "drop")# Mostrar las secuencias más largasresumen_secuencias %>% arrange(desc(cantidad))filtered_amounts <- resumen_secuencias %>% filter(cantidad != 1)```Como referencia, el día 2024-06-27 16:06:00 hay 23 entregas graficadas el mismo día.::: panel-tabset## Ejemplo 50 entregas```{r, warning=FALSE, echo=FALSE} #Cargar librerías necesariaslibrary(leaflet)library(dplyr)# Filtrar las entregas según el fin_visita elegidofin_visita_elegido <- "2024-06-12 16:12:00" # Cambia este valor por el deseadoentregas_filtradas <- data %>% filter(fin_visita == fin_visita_elegido)# Verificar si hay datos para graficarif (nrow(entregas_filtradas) == 0) { print("No hay entregas con el fin_visita seleccionado.")} else { # Crear el mapa interactivo con Leaflet leaflet(data = entregas_filtradas) %>% addTiles() %>% # Añadir un mapa base (OpenStreetMap) addCircleMarkers( lng = ~longitud, lat = ~latitud, # Coordenadas radius = 6, color = "blue", stroke = FALSE, fillOpacity = 0.8, fillColor = "red", # Estilo de los marcadores label = ~paste("Lat:", latitud, "<br>Lng:", longitud), # Etiquetas al pasar el mouse popup = ~paste0("Entrega en: ", latitud, ", ", longitud) # Popup al hacer clic ) }```## Ejemplo 23 entregas```{r, warning=FALSE, echo=FALSE} #Cargar librerías necesariaslibrary(leaflet)library(dplyr)# Filtrar las entregas según el fin_visita elegidofin_visita_elegido <- "2024-06-27 16:06:00" # Cambia este valor por el deseadoentregas_filtradas <- data %>% filter(fin_visita == fin_visita_elegido)# Verificar si hay datos para graficarif (nrow(entregas_filtradas) == 0) { print("No hay entregas con el fin_visita seleccionado.")} else { # Crear el mapa interactivo con Leaflet leaflet(data = entregas_filtradas) %>% addTiles() %>% # Añadir un mapa base (OpenStreetMap) addCircleMarkers( lng = ~longitud, lat = ~latitud, # Coordenadas radius = 6, color = "blue", stroke = FALSE, fillOpacity = 0.8, fillColor = "red", # Estilo de los marcadores label = ~paste("Lat:", latitud, "<br>Lng:", longitud), # Etiquetas al pasar el mouse popup = ~paste0("Entrega en: ", latitud, ", ", longitud) # Popup al hacer clic ) }```:::¿Que tan frecuente es este error? Muy frecuentes```{r, warning=FALSE, echo=FALSE}# Asegurarse de que no haya NAs en la columna 'cantidad'resumen_secuencias <- resumen_secuencias %>% filter(!is.na(cantidad), cantidad != 1)# Crear la columna 'categoria' con las condiciones bien definidasresumen_secuencias <- resumen_secuencias %>% mutate( categoria = case_when( cantidad == 2 ~ "2", cantidad >= 3 & cantidad <= 5 ~ "De 3 a 5", cantidad >= 6 & cantidad <= 10 ~ "De 6 a 10", cantidad >= 11 & cantidad <= 20 ~ "De 11 a 20", cantidad > 20 ~ "Más de 20" ) )# Verificar si hay NAs en la columna 'categoria'resumen_secuencias <- resumen_secuencias %>% filter(!is.na(categoria))# Crear el gráfico de barras con las categorías corregidasggplot(resumen_secuencias, aes(x = categoria)) + geom_bar(fill = "#94C11F", color = "black", alpha = 0.8) + labs( title = "Distribución de Secuencias por Categoría", x = "Categoría de cantidad", y = "Frecuencia" ) + theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Rotar etiquetas del eje X```Con **12799 entregas con horarios mal registrados** representando un **46.67%** los datos disponibles cargados de forma incorrecta.Al desconfiar del 46.67% de los datos es dificil y poco preciso cualquier tipo de analisis sobre la eficiencia operativa de las entregas. Esto puede traer posibles problemas como:1. Problema 12. Problema 2Solucionar esta situacion representa una gran oportunidad y facilitaria exponencialmente el crecimiento de la empresa, precision de las ventanas de entrega, identificación de cuellos de botella reales y a final de cuentas la toma de decisiones operativas.> "Lo que no se mide, no se puede mejorar." - Peter F. DruckerA continuación listamos algunos posibles motivos y oportunidades para corregir la situación.1. Manual de uso y capacitación sobre el sistema [UNIGIS](https://www.unigis.com/ "Sitio web oficial de Unigis")Basandonos en el [Manual de transportistas - Elementos de seguridad y APPS](https://transportes.iflow21.com/portal/es/kb/articles/manual-de-transportistas-elementos-de-seguridad-y-apps) encontramos la siguiente referencia sobre el uso de la aplicación.Capacitar mejor al personal con mayor cantidad de recursos, claridad en los instructivos y videos demostrando el uso correcto del sistema [videos demostrando el uso correcto del sistema](https://www.youtube.com/watch?v=JnEHVHhs6V4 "Video grabado por Corporación Aceros Arequipa para su equipo") mejoraría la precision de la carga de los datos en el futuro.2. Creación de una interfaz personalizada para Unigis.El error de carga de horarios iguales en entregas consecutivas puede deberse principalmente a la dificultad de uso del sistema o poca practicidad del mismo por la que los transportistas podrían saltear los pasos del instructivo.Si migrar a un nuevo sistema más moderno es una alternativa muy costosa podrían considerar hacer una inversión en desarrollo frontend para, utilizando la API del sistema actual, puedan tener una interfaz más amena a los transportistas.[](https://www.postman.com/irampoldi/unigis/request/8833s18/reportedeviajes)El desarrollo de una interfaz personalizada para interactuar con su sistema actual podría ser una inversión considerable pero economica contrastando con la posibilidad de un desarrollo personalizado o la migración a un nuevo sistema.Algunas consideraciones:- Inversión en equipo e investigación UX para asegurar el uso intuitivo de los transportistas. Es importante entender como es el uso del sistema en la practica.3. Migrar a un sistema más moderno o diseñar uno a medida para sus necesidades. Puede ser la opción mas costosa.## Información de bultos y peso.> ¿Cuál es el promedio de bultos, peso y unidades entregadas por pedido?En promedio cada entrega tiene:- 28.40 Unidades- 5.75 Bultos- Un peso de 41.15kg- Un peso promedio elevado por pedido puede indicar productos de gran volumen o de alto consumo. Esto tiene implicaciones en los costos de transporte y las rutas de entrega.::: panel-tabset## 🔢 Unidades::: panel-tabsetEste histograma permite ver la concentración de pedidos en ciertos rangos de unidades. Si se observa una mayor densidad en valores específicos, esto podría indicar patrones de demanda estándar entre los clientes.:::```{r,warning=FALSE,echo=FALSE}# Crear el histogramaggplot(data, aes(x = unidades)) + geom_histogram(binwidth = 50, fill = "#94C11F", color = "black") + labs(title = "Histograma de Unidades", x = "Bultos", y = "Frecuencia") + theme_minimal()``````{r,warning=FALSE,echo=FALSE}mean(data$unidades) # Promedio```## 📦 Bultos::: panel-tabsetEste histograma permite identificar el tamaño típico de los pedidos. Una distribución sesgada hacia valores altos sugiere que la mayoría de los pedidos son de varios bultos, lo que puede influir en la planificación de cargas y en la selección de vehículos de transporte.:::```{r,warning=FALSE,echo=FALSE}# Crear el histogramaggplot(data, aes(x = bultos)) + geom_histogram(binwidth = 5, fill = "#94C11F", color = "black") + labs(title = "Histograma de Bultos", x = "Unidades", y = "Frecuencia") + theme_minimal()``````{r,warning=FALSE,echo=FALSE}mean(data$bultos) # Promedio```## ⚖️ Peso::: panel-tabsetEste histograma permite identificar el tamaño típico de los pedidos. Una distribución sesgada hacia valores altos sugiere que la mayoría de los pedidos son de varios bultos, lo que puede influir en la planificación de cargas y en la selección de vehículos de transporte.:::```{r,warning=FALSE,echo=FALSE}# Crear el histogramaggplot(data, aes(x = peso)) + geom_histogram(binwidth = 50, fill = "#94C11F", color = "black") + labs(title = "Histograma de Peso", x = "Unidades", y = "Frecuencia") + theme_minimal()``````{r,warning=FALSE,echo=FALSE}mean(data$peso) # Promedio```:::## Distribución geográfica de las entregas.> ¿Cuál es la distribución de entregas por localidad o región?```{r,echo=FALSE, output=FALSE}# Geolocalizaciónlibrary(sf)library(raster)``````{r,warning=FALSE,echo=FALSE}data$cliente <- as.factor(data$cliente)# Graficar las entregas por cliente con colores distintosplot_ly( data, lat = ~latitud, lon = ~longitud, type = 'scattermapbox', mode = 'markers', color = ~cliente, # Asigna un color distinto por cliente marker = list(size = 7, opacity = 0.3), # Ajusta el tamaño y la transparencia de los marcadores text = ~paste("Cliente:", cliente, "<br>Dirección:", direccion) # Información al pasar el mouse) %>% layout( mapbox = list( accesstoken = mapbox_token, center = list(lat = -34.6037, lon = -58.3816), # Coordenadas de Buenos Aires zoom = 10, # Nivel de zoom style = "open-street-map" # Estilo del mapa ), title = "Mapa de Entregas en Buenos Aires por Cliente", margin = list(r = 0, t = 0, b = 0, l = 0) )```Como podemos ver este mapa de entregas de la ciudad de Buenos Aires muestra visualmente la dispersión de las entregas en la ciudad. La variedad de colores permite ver qué clientes se agrupan en ciertas áreas. Si ciertos clientes concentran entregas en una región específica, eso puede indicar una demanda localizada o preferencia regional de ciertos productos.```{r,warning=FALSE,echo=FALSE}# Crear una lista de fechas únicas extraídas de la columna fin_visitafechas_unicas <- unique(as.Date(data$fin_visita))# Graficar las entregas por cliente con un slider para cambiar por fechaplot_ly( data, lat = ~latitud, lon = ~longitud, type = 'scattermapbox', mode = 'markers', color = ~cliente, # Asigna un color distinto por cliente frame = ~as.Date(fin_visita), # Agregar la fecha como frame para la animación marker = list(size = 7, opacity = 0.7), # Ajusta el tamaño y la transparencia de los marcadores text = ~paste("Cliente:", cliente, "<br>Dirección:", direccion) # Información al pasar el mouse) %>% layout( mapbox = list( accesstoken = mapbox_token, center = list(lat = -34.6037, lon = -58.3816), # Coordenadas de Buenos Aires zoom = 10, # Nivel de zoom style = "open-street-map" # Estilo del mapa ), title = "Mapa de Entregas en Buenos Aires por Cliente", margin = list(r = 0, t = 0, b = 0, l = 0) ) %>% animation_opts( frame = 500, # Duración de cada frame en milisegundos transition = 0, # Sin transiciones entre frames redraw = TRUE ) %>% animation_slider( currentvalue = list(prefix = "Fecha: ") )```En este mapa las áreas con mayor densidad de entregas resaltan visualmente. Estas zonas de alta actividad pueden representar zonas comerciales o residenciales clave. Esto es útil para optimizar las rutas de entrega y asignar más recursos a zonas con alta demanda.Para verlo de forma más resumida;::: panel-tabset## 🏘️ BarriosEn base a este mapa se permite ver cómo se distribuyen las entregas a nivel de barrio. Los barrios con mayor intensidad de color son aquellos con más entregas, lo que indica la importancia de estos barrios en el volumen de pedidos.```{r,warning=FALSE,echo=FALSE}# Cargar los barrios desde el archivo GeoJSONbarrios.comp <- st_read("maps/barrios.geojson", quiet = TRUE) # Reemplaza con la ruta correctabarrios <- barrios.comp[, c("BARRIO", "geometry")]# Ver los nombres de las columnas del GeoDataFrame de barrios# Filtrar las entregas con coordenadas válidas y crear un objeto sfdata_sf <- data %>% filter(!is.na(latitud) & !is.na(longitud)) %>% st_as_sf(coords = c("longitud", "latitud"), crs = 4326) # Sistema de coordenadas WGS 84# Unir cada entrega con su barrio correspondienteentregas_por_barrio <- st_join(data_sf, barrios)# Agrupar por el campo "BARRIO" y contar el total de entregasentregas_agrupadas <- entregas_por_barrio %>% group_by(BARRIO) %>% summarise(total_entregas = n())# Unir la información agregada de entregas al GeoDataFrame de barriosbarrios <- barrios %>% st_join(entregas_agrupadas)# Rellenar valores NA (barrios sin entregas) con 0barrios$total_entregas[is.na(barrios$total_entregas)] <- 0# Crear el mapa con ggplot2ggplot(data = barrios) + geom_sf(aes(fill = total_entregas)) + # Colorear según la cantidad de entregas scale_fill_viridis_c(option = "plasma", na.value = "white") + # Paleta de colores theme_minimal() + labs( title = "Cantidad de Entregas por Barrio en Buenos Aires", fill = "Entregas" )```## 🏙️ ComunasSimilar al mapa por barrio, pero agrupando entregas por comunas. Las comunas que presentan más entregas destacan como puntos de interés para analizar el impacto logístico y la demanda concentrada.```{r, warning=FALSE, echo=FALSE}# 1. Cargar los barrios desde el archivo GeoJSONbarrios.comp <- st_read("maps/barrios.geojson", quiet = TRUE) # Ajusta la ruta según corresponda# 2. Agrupar los polígonos por "COMUNA"comunas <- barrios.comp %>% group_by(COMUNA) %>% summarise(geometry = st_union(geometry)) # Unir los polígonos por comuna# Asegurarse de que COMUNA sea textocomunas$COMUNA <- as.character(comunas$COMUNA)# 3. Filtrar las entregas con coordenadas válidas y convertirlas a un objeto sfdata_sf <- data %>% filter(!is.na(latitud) & !is.na(longitud)) %>% st_as_sf(coords = c("longitud", "latitud"), crs = 4326)# 4. Asignar cada entrega a su comuna correspondiente usando st_joinentregas_por_comuna <- st_join(data_sf, comunas)# 5. Agrupar por "COMUNA" y contar el total de entregasentregas_agrupadas <- entregas_por_comuna %>% group_by(COMUNA) %>% summarise(total_entregas = n())# 6. Unir los datos de entregas agregados al GeoDataFrame de comunascomunas <- comunas %>% st_join(entregas_agrupadas)# 7. Rellenar los valores NA (comunas sin entregas) con 0comunas$total_entregas[is.na(comunas$total_entregas)] <- 0# 8. Crear el mapa con ggplot2ggplot(data = comunas) + geom_sf(aes(fill = total_entregas)) + scale_fill_viridis_c(option = "plasma", na.value = "white") + theme_minimal() + labs( title = "Cantidad de Entregas por Comuna en Buenos Aires", fill = "Entregas" )```## 🌡️ Entregas IndividualesLos puntos en el mapa de Buenos Aires representan ubicaciones de entregas, y las áreas con mayor densidad de entregas aparecen con colores más intensos, permitiendo identificar visualmente las zonas con más actividad de entregas.```{r, warning=FALSE, echo=FALSE}# Mapa de calor de entregasheatmap_data <- data %>% group_by(latitud, longitud) %>% summarise(total_entregas = n())# Graficar un mapa de calor para visualizar las zonas con mayor densidad de entregasheatmap_plot <- plot_ly( heatmap_data, lat = ~latitud, lon = ~longitud, z = ~total_entregas, type = 'densitymapbox', colorscale = 'Viridis', radius = 10) %>% layout( mapbox = list( accesstoken = mapbox_token, center = list(lat = -34.6037, lon = -58.3816), zoom = 10, style = "open-street-map" ), title = "Mapa de Calor de Entregas en Buenos Aires", showlegend = FALSE, # Ocultar la leyenda margin = list(r = 0, t = 60, b = 0, l = 0) # Agregar más espacio en la parte superior )# Mostrar el mapa de calorheatmap_plot```:::### PENDIENTE Centro de distribución en Mendoza.> mostrar grafico, imagen de google maps y explicación.::: panel-tabset## 📍 Mapa```{r,warning=FALSE,echo=FALSE}# Crear un gráfico usando Plotly y Mapboxfig <- plot_ly( data = data, type = 'scattermapbox', mode = 'markers', lat = ~latitud, lon = ~longitud, marker = list(size = 8, color = 'blue', opacity = 0.7))# Configurar el estilo de Mapbox (puedes cambiar el estilo)fig <- fig %>% layout( mapbox = list( style = 'carto-positron', # Otros estilos: 'open-street-map', 'stamen-terrain', etc. zoom = 2, # Nivel de zoom center = list(lat = mean(data$latitud), lon = mean(data$longitud)) # Centrado en los datos ), margin = list(t = 0, b = 0, l = 0, r = 0) # Margen para ajustar el espacio del gráfico )# Mostrar el gráficofig```## 🖼️ Screenshot:::> ¿Cuántas entregas se hicieron fuera del tiempo esperado o planificado?```{r, warning=FALSE, echo=FALSE}# Crear una nueva columna que clasifique si llegó tarde o tempranodata <- data %>% mutate(estado_entrega = ifelse(diferencia_minutos > 0, "Tarde", "Temprano o a Tiempo"))# Agrupar los datos por mes y estado de entregadata_agrupada <- data %>% group_by(mes, estado_entrega) %>% summarise(cantidad = n())# Graficar con ggplotggplot(data_agrupada, aes(x = mes, y = cantidad, fill = estado_entrega)) + geom_bar(stat = "identity", position = "dodge") + labs( title = "Cantidad de Entregas por Mes", x = "Mes", y = "Cantidad de Entregas", fill = "Estado de Entrega" ) + theme_minimal()```Este gráfico permite la visualización de la cantidad de entregas por mes, clasificadas en "Tarde" y "Temprano o a Tiempo".> ¿Qué clientes generan más volumen de entregas y cuáles presentan más problemas o irregularidades?```{r, warning=FALSE, echo=FALSE}# Crear el mapa con plotly y mapboxfig <- plot_ly( data = data, lat = ~latitud, lon = ~longitud, color = ~factor(cliente), # Colorear según cliente colors = c("dodgerblue", "tomato"), # Colores personalizados para cada cliente type = 'scattermapbox', mode = 'markers', marker = list( size = 10, # Tamaño del marcador opacity = 0.2 # Ajustar transparencia (alpha) ), text = ~paste( "Latitud: ", latitud, "<br>", "Longitud: ", longitud, "<br>", "Cliente: ", cliente, "<br>", "ID Orden: ", id_orden, "<br>", "Fecha: ", fin_visita ), # Información a mostrar en hover hoverinfo = 'text' # Mostrar solo el texto personalizado)# Configuración del mapa centrado en Buenos Airesfig <- fig %>% layout( mapbox = list( style = 'open-street-map', # Estilo del mapa zoom = 10, # Ajuste del nivel de zoom para Buenos Aires center = list(lat = -34.6037, lon = -58.3816) # Centrar en Buenos Aires ), title = "Mapa de Entregas por Cliente" )# Mostrar el mapafig```Este mapa permite la visualización de un mapa interactivo que muestra las entregas realizadas, clasificado por cliente, en Buenos Aires.## Conclusiones# Segmentación y patrones en entregas.## PENDIENTE Volumen y Distribución de Entregas> ¿Cuántas entregas corresponden a cada cliente?```{r, warning=FALSE, echo=FALSE}# Crear barchart con la cantidad total de entregas por clientebarchart_cliente <- data %>% group_by(cliente) %>% summarise(cantidad = n()) %>% ggplot(aes(x = factor(cliente), y = cantidad, fill = factor(cliente))) + # Barras con color personalizado geom_bar(stat = "identity") + # Mostrar número de entregas arriba de cada barra geom_text(aes(label = cantidad), vjust = -0.5, size = 5) + # Personalizar colores de las barras scale_fill_manual(values = c("20" = "dodgerblue", "70" = "tomato")) + # Títulos y etiquetas labs( title = "Cantidad de Entregas por Cliente", x = "Cliente", y = "Cantidad de Entregas" ) + # Tema del gráfico theme_minimal() + theme(legend.position = "none") # Ocultar leyenda si los colores coinciden con los clientes# Mostrar el gráficoprint(barchart_cliente)```Este gráfico de barras permite la visualización de la cantidad total de entregas realizadas por cada cliente, utilizando barras que muestran el número de entregas para cada uno> Identificar entregas recurrentes a domicilios,```{r, warning=FALSE, echo=FALSE}# Agrupar y contar la cantidad de entregas por cliente y día de la semanaentregas_por_cliente_dia <- data %>% filter(!is.na(dia_str) & dia_str != "domingo") %>% group_by(cliente, dia_str) %>% summarise(cantidad = n()) %>% ungroup() %>% mutate(dia_str = factor(dia_str, levels = c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado")))# Line chart de cantidad de entregas por cliente y día de la semanalinechart_cliente_dia <- entregas_por_cliente_dia %>% ggplot(aes(x = dia_str, y = cantidad, color = factor(cliente), group = cliente)) + geom_line(size = 1) + geom_point(size = 3) + labs(title = "Cantidad de Entregas por Día de la Semana y Cliente", x = "Día de la Semana", y = "Cantidad de Entregas", color = "Cliente") + theme_minimal() + theme(legend.position = "bottom")# Mostrar el gráficoprint(linechart_cliente_dia)```Este gráfico de lineas permite la visualización de la cantidad de entregas realizadas por cliente a lo largo de los días de la semana, excluyendo el domingo. Utiliza líneas para representar la evolución del número de entregas para cada cliente, con puntos destacados en cada día.> Los domicilios reciben entregas de un único cliente o solo 20 o 70?Total de ordenes con más de una entrega: 5027::: panel-tabset## Distribución```{r, warning=FALSE, echo=FALSE}entregas_por_orden <- data %>% group_by(id_orden) %>% summarize(cant_entregas = n(), has_many = cant_entregas > 1, clientes_unicos = n_distinct(cliente), .groups = "drop")# Crear el histogramaggplot(entregas_por_orden, aes(x = cant_entregas)) + geom_histogram(binwidth = 1, fill = "#94C11F", color = "#272727") + labs(title = "Histograma de Entregas por domicilio", x = "Valor", y = "Cantidad de entregas") + theme_minimal()```Este gráfico permite la visualización de la distribución de la cantidad de entregas por domicilio, mostrando un histograma que representa la frecuencia de entregas para diferentes valores.## Porcentajes```{r, warning=FALSE, echo=FALSE}# Calcular las frecuencias y porcentajesfrecuencias <- entregas_por_orden %>% count(has_many) %>% mutate(porcentaje = (n / sum(n)) * 100)# Crear el gráfico de barras con porcentajes encimaggplot(frecuencias, aes(x = has_many, y = n)) + geom_bar(stat = "identity", fill = "#94C11F", color = "#272727") + geom_text(aes(label = paste0(round(porcentaje, 1), "%")), vjust = -0.5, size = 3) + # Ajuste para que el texto aparezca encima labs(title = "Domicilios con más de una entrega", x = "¿Tiene más de una entrega?", y = "Cantidad de resultados") + theme_minimal()```Este gráfico de barra permite la visualización de la cantidad de domicilios que han recibido más de una entrega. Utiliza barras para representar el número total de domicilios, distinguiendo entre aquellos que tienen mas de una entrega y los que no.:::> ¿Qué porcentaje de entregas se concentra en las localidades más activas?>> > zonas de entregas.```{r, warning=FALSE, echo=FALSE}# Filtrar los datos de Buenos Airesbuenos_aires_data <- data %>% filter(latitud >= -35.2, latitud <= -34.3, longitud >= -58.8, longitud <= -57.9)# Extraer las coordenadascoords <- buenos_aires_data %>% dplyr::select(latitud, longitud)# Método del codo para determinar el número óptimo de clustersset.seed(123)wss_values <- sapply(1:10, function(k) { kmeans(coords, centers = k, nstart = 10)$tot.withinss})# Graficar el método del codo con ggplot2elbow_plot <- data.frame(K = 1:10, WSS = wss_values) %>% ggplot(aes(x = K, y = WSS)) + geom_point() + geom_line() + geom_vline(xintercept = 4, linetype = "dashed", color = "red") + annotate("text", x = 4.2, y = wss_values[4] + 10, label = "Elección óptima de K", color = "red", hjust = 0) + labs(title = "Método del codo para determinar K óptimo", x = "Número de Clusters K", y = "Suma de cuadrados dentro del cluster (WSS)") + theme_minimal()# Aplicar K-means con K = 4set.seed(123)kmeans_result <- kmeans(coords, centers = 4, nstart = 10)# Agregar los clusters al DataFrame originalbuenos_aires_data <- buenos_aires_data %>% mutate(cluster = as.factor(kmeans_result$cluster))```::: panel-tabset## Resultado```{r, warning=FALSE, echo=FALSE}# Visualizar los clusters en un mapa interactivo con leafletpalette <- colorFactor(palette = "Set1", domain = buenos_aires_data$cluster)leaflet(data = buenos_aires_data) %>% addTiles() %>% addCircleMarkers(~longitud, ~latitud, color = ~palette(cluster), radius = 2, fillOpacity = 0.8, popup = ~paste("Cluster:", cluster)) %>% addLegend("bottomright", pal = palette, values = ~cluster, title = "Clusters", opacity = 0.2)```## Elbow```{r, warning=FALSE, echo=FALSE}print(elbow_plot)```:::::: panel-tabset## Entregas por zona```{r, warning=FALSE, echo=FALSE}# Calcular el número de entregas por cluster y ordenarlos de mayor a menorcluster_counts <- buenos_aires_data %>% group_by(cluster) %>% summarise(count = n(), .groups = "drop") %>% arrange(desc(count))# Graficar el bar chart de cantidad de entregas por cluster con etiquetasggplot(cluster_counts, aes(x = reorder(cluster, -count), y = count, fill = cluster)) + geom_bar(stat = "identity") + geom_text(aes(label = count), vjust = -0.5, size = 5) + labs(title = "Cantidad de entregas por cluster", x = "Cluster", y = "Cantidad de entregas") + theme_minimal() + theme(legend.position = "none") + ylim(0, max(cluster_counts$count) * 1.1) # Aumenta el límite superior del eje y```Este gráfico permite la visualización de cuántas entregas se han realizado en cada cluster, mostrando la cantidad de entregas en un gráfico de barras. Las barras están ordenadas de mayor a menor, lo que facilita ver rápidamente en qué clusters hay más actividad## Cobertura```{r, warning=FALSE, echo=FALSE}# Convertir el DataFrame a un objeto espacial sfbuenos_aires_sf <- buenos_aires_data %>% st_as_sf(coords = c("longitud", "latitud"), crs = 4326)# Calcular el envolvente convexo (convex hull) para cada clusterclusters_hulls <- buenos_aires_sf %>% group_by(cluster) %>% summarise(geometry = st_combine(geometry), .groups = "drop") %>% # Combina las geometrías de cada cluster st_convex_hull() # Calcula el envolvente convexo# Calcular el número de puntos en cada clustercluster_counts <- buenos_aires_data %>% group_by(cluster) %>% summarise(count = n())# Unir los conteos con los envolventes convexosclusters_hulls <- clusters_hulls %>% left_join(cluster_counts, by = "cluster")# Obtener el centroide de cada polígono de cluster para posicionar las etiquetasclusters_hulls_centroids <- st_centroid(clusters_hulls)# Graficarggplot() + # Graficar los envolventes convexos geom_sf(data = clusters_hulls, aes(fill = cluster), alpha = 0.3, color = NA) + # Graficar los puntos de datos geom_sf(data = buenos_aires_sf, aes(color = cluster), size = 0.5) + # Agregar etiquetas con el número de puntos en cada cluster geom_text(data = clusters_hulls_centroids, aes(x = st_coordinates(geometry)[,1], y = st_coordinates(geometry)[,2], label = count), size = 5, color = "black") + labs(title = "Áreas cubiertas por cada cluster en Buenos Aires", fill = "Cluster", color = "Cluster") + theme_minimal() + coord_sf()```Este mapa permite la visualización de las áreas cubiertas por cada cluster en Buenos Aires, utilizando envolventes convexos para representar la extensión de cada grupo de entregas. En él, se muestran los puntos de datos de las entregas, coloreados según su cluster, lo que facilita identificar visualmente la distribución geográfica.:::```{r, warning=FALSE, echo=FALSE}# Calcular el número de entregas por dia_str y por clusterdeliveries_per_day_cluster <- buenos_aires_data %>% group_by(cluster, dia_str) %>% summarise(count = n()) %>% ungroup()# Graficar el bar chart de cantidad de entregas por dia_str y por clusterggplot(deliveries_per_day_cluster, aes(x = dia_str, y = count, fill = cluster)) + geom_bar(stat = "identity", position = "dodge") + labs(title = "Cantidad de entregas por día y por cluster", x = "Día", y = "Cantidad de entregas", fill = "Cluster") + theme_minimal() + theme(axis.text.x = element_text(angle = 45, hjust = 1))```Este grafico de barras muestra la cantidad de entregas por dia de la semana y por cluster. El gráfico permite comparar fácilmente la cantidad de entregas realizadas en cada día, desglosadas por los diferentes clusters identificados en Buenos Aires.```{r, warning=FALSE, echo=FALSE}# Calcular el número de entregas por cluster y por clientedeliveries_per_cluster_cliente <- buenos_aires_data %>% group_by(cluster, cliente) %>% summarise(count = n()) %>% ungroup()# Graficar el bar chart de cantidad de entregas por cluster y por clienteggplot(deliveries_per_cluster_cliente, aes(x = cluster, y = count, fill = factor(cliente))) + geom_bar(stat = "identity", position = "dodge") + labs(title = "Cantidad de entregas por cluster y por cliente", x = "Cluster", y = "Cantidad de entregas", fill = "Cliente") + theme_minimal()```Este gráfico de barras permite visualizar la cantidad de entregas por cluster, mostrando en el eje X los diferentes clusters y en el eje Y el número total de entregas, con cada barra coloreada según el cliente para facilitar la comparación de la distribución de entregas en Buenos Aires.## PENDIENTE Eficiencia y Rendimiento Operativo> ¿Cuál es el tiempo promedio de entrega por cliente y por localidad?>> ¿Qué zonas presentan mayores retrasos o entregas fuera de tiempo?>> > Identifica áreas con posibles cuellos de botella logísticos.>> ¿Se detectan diferencias significativas en los tiempos de entrega según la cantidad de bultos o peso?# Análisis de series temporales```{r, warning=FALSE, echo=FALSE}# Aseguramos que la columna 'mes' tenga solo el año y mesdata_resumida <- data %>% mutate(mes = as.Date(format(mes, "%Y-%m-01")))# Agrupación de los datos por día y mesdata_resumida <- data_resumida %>% group_by(mes, dia) %>% summarize(n = n(), .groups = 'drop')# Crear una fecha combinando el día y el mesdata_resumida <- data_resumida %>% mutate(fecha = mes + days(dia - 1)) %>% arrange(fecha) # Ordenar por la fecha completa para evitar saltos# Crear el gráfico interactivo con Plotlyfig <- plot_ly(data_resumida, x = ~fecha, y = ~n, type = 'scatter', mode = 'lines+markers') %>% layout( title = "Evolución de n a lo largo del tiempo", xaxis = list( title = "Fecha", tickformat = "%d-%b", # Formato de fecha tickangle = 45 # Rotar las etiquetas del eje X ), yaxis = list(title = "n"), margin = list(b = 80) # Asegurar espacio para las etiquetas del eje X )# Mostrar el gráficofig```Tenemos 4 grandes caidas en las entregas por lo que buscamos identificar el motivo en cada fecha.| Fecha | Cantidad de entregas | Motivo ||------------------|------------------|-------------------------------------|| Jueves 9 de mayo | 0 | FNTC (Camioneros) adhiere al [paro general del 9 de mayo\*](https://tn.com.ar/sociedad/2024/05/08/quienes-adhieren-al-paro-general-del-9-de-mayo-y-que-servicios-no-funcionan/). || Jueves 20 de junio | 138 | [Feriado nacional\*](https://www.argentina.gob.ar/interior/feriados-nacionales-2024) Paso a la Inmortalidad del Gral. Manuel Belgrano || **Domingo** 21 de julio | 3 | Como [explicamos anteriormente](#horarios-entrega "¿Cuales son los horarios de entrega?") , según nuestros datos iflow no realizan entregas los domingos. || 2 de agosto | 11 | Por simplicidad asumimos que se debe a falta de datos en la fecha para comparir el dataset. |Para realizar un pronóstico de demanda para el mes de agosto vamos no vamos a tomar en cuenta estos 4 casos asumiendolos como irrelevantes para el modelo. Limitando los datos hasta el 30 de julio```{r, warning=FALSE, echo=FALSE}data_resumida_clean <- data_resumida %>% filter( !(fecha == as.Date("2024-05-09")), !(fecha == as.Date("2024-06-20")), !(fecha == as.Date("2024-07-21")), !(fecha > as.Date("2024-07-30")) )# Crear el gráfico interactivo con Plotlyfig <- plot_ly(data_resumida_clean, x = ~fecha, y = ~n, type = 'scatter', mode = 'lines+markers') %>% layout( title = "Evolución de n a lo largo del tiempo", xaxis = list( title = "Fecha", tickformat = "%d-%b", # Formato de fecha tickangle = 45 # Rotar las etiquetas del eje X ), yaxis = list(title = "n"), margin = list(b = 80) # Asegurar espacio para las etiquetas del eje X )# Mostrar el gráficofig```Con los datos ordenados ahora podemos descomponer la serie temporal en tendencia, estacionalidad y residuales.```{r, warning=FALSE, echo=FALSE}# Convertir los datos a serie temporal (suponiendo frecuencia diaria)ts_data <- ts(data_resumida_clean$n, frequency = 7) # Frecuencia semanal# Descomposición de la serie temporaldescomposicion <- stl(ts_data, s.window = "periodic")# Gráfico de la descomposiciónplot(descomposicion)``````{r, warning=FALSE, echo=FALSE}# Filtrar los datos para entrenamiento y validacióntrain_data <- data_resumida_clean %>% filter(fecha >= as.Date("2024-05-01") & fecha <= as.Date("2024-06-30"))test_data <- data_resumida_clean %>% filter(fecha >= as.Date("2024-07-01") & fecha <= as.Date("2024-07-31"))# Convertir los datos de entrenamiento y validación a series temporalestrain_ts <- ts(train_data$n, start = c(2024, 5), frequency = 7)test_ts <- ts(test_data$n, start = c(2024, 7), frequency = 7)# Ajustar los modelos ARIMA(1,0,1) y ARIMA(1,1,1)modelo_arima_101 <- Arima(train_ts, order = c(1,0,1))print(modelo_arima_101)modelo_arima_111 <- Arima(train_ts, order = c(1,1,1))print(modelo_arima_111)# Predecir para el mismo número de pasos que los datos de pruebapred_101 <- forecast(modelo_arima_101, h = length(test_ts))pred_111 <- forecast(modelo_arima_111, h = length(test_ts))# Asegurar que las predicciones y los datos de prueba tengan la misma longitudif (length(test_ts) == length(pred_101$mean)) { # Reemplazar valores NA en predicciones, si los hubiera pred_101$mean[is.na(pred_101$mean)] <- 0 pred_111$mean[is.na(pred_111$mean)] <- 0 # Alinear las predicciones y los datos de prueba aligned_pred_101 <- ts(pred_101$mean, start = start(test_ts), frequency = 7) aligned_pred_111 <- ts(pred_111$mean, start = start(test_ts), frequency = 7) # Calcular errores para ARIMA(1,0,1) MAE_101 <- mean(abs(test_ts - aligned_pred_101), na.rm = TRUE) RMSE_101 <- sqrt(mean((test_ts - aligned_pred_101)^2, na.rm = TRUE)) # Calcular errores para ARIMA(1,1,1) MAE_111 <- mean(abs(test_ts - aligned_pred_111), na.rm = TRUE) RMSE_111 <- sqrt(mean((test_ts - aligned_pred_111)^2, na.rm = TRUE)) # Mostrar resultados cat("Errores ARIMA(1,0,1):\n MAE:", MAE_101, "\n RMSE:", RMSE_101, "\n") cat("Errores ARIMA(1,1,1):\n MAE:", MAE_111, "\n RMSE:", RMSE_111, "\n")} else { cat("Error: Las longitudes de las predicciones y los datos de prueba no coinciden.\n")}```# Unidades de TransporteEn los datos proporcionados no contamos con la información necesaria para realizar un análisis específico de las unidades de transporte, ya que sería indispensable disponer de un identificador único para cada vehículo. Sin embargo, incluimos esta sección como prueba de concepto para demostrar el valor que podría generar este tipo de análisis en la operación logística. Disponer de esta información permitiría evaluar aspectos fundamentales de la gestión de la flota, optimización de rutas y eficiencia operativa.A continuación, presentamos algunas preguntas clave que podrían responderse con un análisis detallado de las unidades de transporte:## **Preguntas sobre Desempeño y Utilización de la Flota**1. **¿Cuánto tiempo real dedica cada unidad a entregas versus tiempo muerto (espera, carga, mantenimiento)?**2. **¿Cuáles son los tiempos de ruta promedio por camión y cómo varían según la región?**3. **¿Es necesaria la cantidad actual de camiones, o existe capacidad ociosa que podría aprovecharse?**4. **¿Hay rutas o zonas específicas donde sería más eficiente reducir o ampliar la flota?**5. **¿Qué porcentaje de las unidades completan sus rutas dentro de los tiempos planificados?**#### **Preguntas sobre Optimización de Rutas y Rendimiento**6. **¿Se podrían consolidar entregas para reducir la cantidad de viajes sin afectar el servicio?**7. **¿Existen unidades con rutas ineficientes que podrían optimizarse con ajustes?**8. **¿Cuál es la relación entre la distancia recorrida y el volumen entregado por unidad?**9. **¿Se podrían reducir tiempos muertos al mejorar la planificación de entregas o las ventanas horarias?**Si bien no contamos con la información completa sobre las unidades de transporte, hemos realizado estimaciones basadas en los datos disponibles y presentamos nuestro análisis como aproximación para obtener insights relevantes.> Ver las sedes de Iflow (que conocemos)```{r, warning=FALSE, echo=FALSE}# Definir las coordenadas en la variable 'sedes'sedes <- data.frame( lat = c(-34.4574168, -34.4312098, -34.4616837, -34.4616837), lon = c(-58.7347338, -58.7254967, -58.7390698, -58.7390698), zoom = c(14, NA, NA, 17) # Columna opcional para definir niveles de zoom)# Crear el mapa interactivo usando Plotly y Mapboxfig <- plot_ly( data = sedes, type = 'scattermapbox', # Define el tipo de gráfico lat = ~lat, lon = ~lon, mode = 'markers', # Establece los puntos como marcadores marker = list(size = 10, color = 'red') # Opcional: personalización de marcadores)# Añadir el estilo de Mapboxfig <- fig %>% layout( mapbox = list( style = 'open-street-map', # Puedes usar otros estilos como 'streets', 'satellite' zoom = 10, # Nivel de zoom inicial center = list(lat = mean(sedes$lat), lon = mean(sedes$lon)) # Centrar el mapa ) )# Mostrar el gráficofig```> Graficar la primera entrega de cada día. Graficar la primera y segunda entrega de cada día. Esperariamos que estas entregas rodeen cada sede. Entender su comportamiento.```{r, warning=FALSE, echo=FALSE}# Suponiendo que 'data' tiene las columnas necesarias: 'fin_visita', 'latitud', 'longitud', 'cliente'primeras_entregas <- data %>% mutate(fecha = as.Date(fin_visita)) %>% group_by(fecha) %>% filter(fin_visita == min(fin_visita)) %>% ungroup()# Crear un mapa interactivo con Leafletmapa_leaflet <- leaflet() %>% addTiles() %>% # Agregar marcadores para las primeras entregas en azul addCircleMarkers( data = primeras_entregas, ~longitud, ~latitud, radius = 3, color = "blue", fillOpacity = 0.8, popup = ~paste("Fecha:", fecha, "<br>Cliente:", cliente, "<br>Hora:", fin_visita) ) %>% # Agregar marcadores para las sedes en rojo addCircleMarkers( data = sedes, ~lon, ~lat, radius = 5, color = "red", fillOpacity = 1, popup = ~paste("Sede") ) %>% # Agregar la leyenda addLegend( "bottomright", colors = c("blue", "red"), labels = c("Primera Entrega", "Sede"), title = "Leyenda" )# Mostrar el mapamapa_leaflet```Primera y segunda entrega de cada día```{r, warning=FALSE, echo=FALSE}# Encontrar la primera y la segunda entrega de cada díaentregas_dia <- data %>% mutate(fecha = as.Date(fin_visita)) %>% group_by(fecha) %>% arrange(fin_visita) %>% # Ordenar por hora de finalización slice(1:2) %>% # Seleccionar las dos primeras entregas mutate(tipo = ifelse(row_number() == 1, "Primera Entrega", "Segunda Entrega")) %>% ungroup()# Crear el mapa interactivo con Leafletmapa_leaflet <- leaflet(data = entregas_dia) %>% addTiles() %>% addCircleMarkers( data = entregas_dia %>% filter(tipo == "Primera Entrega"), ~longitud, ~latitud, radius = 3, color = "blue", fillOpacity = 0.8, popup = ~paste("Fecha:", fecha, "<br>Cliente:", cliente, "<br>Hora:", fin_visita) ) %>% addCircleMarkers( data = entregas_dia %>% filter(tipo == "Segunda Entrega"), ~longitud, ~latitud, radius = 5, color = "red", fillOpacity = 0.8, popup = ~paste("Fecha:", fecha, "<br>Cliente:", cliente, "<br>Hora:", fin_visita) ) %>% addLegend( "bottomright", colors = c("blue", "red"), labels = c("Primera Entrega", "Segunda Entrega"), title = "Leyenda" )# Mostrar el mapamapa_leaflet```> Grafico de orden de las entregas de un día en específico.```{r, warning=FALSE, echo=FALSE}# Parámetros: Selección de fecha específica y cantidad de entregas fecha_seleccionada <- as.Date("2024-05-23") n_entregas <- 20```::: panel-tabset## Ocultar tiempo entre entregas```{r, warning=FALSE, echo=FALSE}# Filtrar las entregas para la fecha seleccionada y ordenarlas por horaentregas_fecha <- data %>% filter(as.Date(fin_visita) == fecha_seleccionada) %>% arrange(fin_visita) %>% slice(1:n_entregas) %>% mutate( orden = row_number(), # Agregar el orden de entrega tiempo_transcurrido = c(NA, diff(fin_visita) / 60) # Calcular minutos entre entregas )# Crear la paleta de colores basada en el valor del clientepaleta_colores <- colorFactor( palette = c("red", "green"), # Asignar colores específicos domain = entregas_fecha$cliente # Basado en los valores de la columna cliente)# Crear etiquetas de tiempo entre entregas para colocarlas en las líneasetiquetas_lineas <- entregas_fecha %>% filter(!is.na(tiempo_transcurrido)) %>% # Excluir la primera entrega (sin tiempo anterior) mutate( lat_medio = (latitud + lag(latitud)) / 2, lng_medio = (longitud + lag(longitud)) / 2, etiqueta_tiempo = paste0(round(tiempo_transcurrido, 2), " min") ) %>% na.omit() # Remover filas con NA (la primera fila)# Crear el mapa interactivo con Leafletmapa_leaflet <- leaflet(data = entregas_fecha) %>% addTiles() %>% # Agregar puntos con colores basados en el cliente y etiquetas personalizadas addCircleMarkers( ~longitud, ~latitud, radius = 8, color = "black", fillColor = ~paleta_colores(cliente), fillOpacity = 0.8, label = ~as.character(orden), # Mostrar el número de orden como etiqueta labelOptions = labelOptions( noHide = TRUE, direction = "center", textOnly = TRUE, style = list( "font-size" = "13px", "font-weight" = "bold", "color" = "white", "border-radius" = "100%", "padding" = "12px" ) ), # Mostrar la hora exacta y tiempo transcurrido como popup popup = ~paste( "Hora de Entrega:", format(fin_visita, "%H:%M:%S"), "<br>Cliente:", cliente, "<br>Tiempo desde la última entrega:", ifelse(is.na(tiempo_transcurrido), "N/A", paste(round(tiempo_transcurrido, 2), "min")) ) ) %>% # Agregar líneas que conecten las entregas en orden addPolylines( lng = ~longitud, lat = ~latitud, data = entregas_fecha, color = "blue", weight = 2 ) %>% # Agregar una leyenda para los clientes addLegend( "bottomright", pal = paleta_colores, values = ~cliente, title = "Cliente", opacity = 1 )# Mostrar el mapamapa_leaflet```## Mostrar tiempo entre entregas```{r, warning=FALSE, echo=FALSE}# Filtrar las entregas para la fecha seleccionada y ordenarlas por horaentregas_fecha <- data %>% filter(as.Date(fin_visita) == fecha_seleccionada) %>% arrange(fin_visita) %>% slice(1:n_entregas) %>% mutate( orden = row_number(), # Agregar el orden de entrega tiempo_transcurrido = c(NA, diff(fin_visita) / 60) # Calcular minutos entre entregas )# Crear la paleta de colores basada en el valor del clientepaleta_colores <- colorFactor( palette = c("red", "green"), # Asignar colores específicos domain = entregas_fecha$cliente # Basado en los valores de la columna cliente)# Crear etiquetas de tiempo entre entregas para colocarlas en las líneasetiquetas_lineas <- entregas_fecha %>% filter(!is.na(tiempo_transcurrido)) %>% # Excluir la primera entrega (sin tiempo anterior) mutate( lat_medio = (latitud + lag(latitud)) / 2, lng_medio = (longitud + lag(longitud)) / 2, etiqueta_tiempo = paste0(round(tiempo_transcurrido, 2), " min") ) %>% na.omit() # Remover filas con NA (la primera fila)# Crear el mapa interactivo con Leafletmapa_leaflet <- leaflet(data = entregas_fecha) %>% addTiles() %>% # Agregar puntos con colores basados en el cliente y etiquetas personalizadas addCircleMarkers( ~longitud, ~latitud, radius = 8, color = "black", fillColor = ~paleta_colores(cliente), fillOpacity = 0.8, label = ~as.character(orden), # Mostrar el número de orden como etiqueta labelOptions = labelOptions( noHide = TRUE, direction = "center", textOnly = TRUE, style = list( "font-size" = "13px", "font-weight" = "bold", "color" = "white", "border-radius" = "100%", "padding" = "12px" ) ), # Mostrar la hora exacta y tiempo transcurrido como popup popup = ~paste( "Hora de Entrega:", format(fin_visita, "%H:%M:%S"), "<br>Cliente:", cliente, "<br>Tiempo desde la última entrega:", ifelse(is.na(tiempo_transcurrido), "N/A", paste(round(tiempo_transcurrido, 2), "min")) ) ) %>% # Agregar líneas que conecten las entregas en orden addPolylines( lng = ~longitud, lat = ~latitud, data = entregas_fecha, color = "blue", weight = 2 ) %>% # Agregar etiquetas sobre las líneas con tiempo transcurrido addLabelOnlyMarkers( lng = ~lng_medio, lat = ~lat_medio, label = ~etiqueta_tiempo, data = etiquetas_lineas, labelOptions = labelOptions( noHide = TRUE, direction = "top", style = list( "font-size" = "10px", "font-weight" = "bold", "color" = "blue" ) ) ) %>% # Agregar una leyenda para los clientes addLegend( "bottomright", pal = paleta_colores, values = ~cliente, title = "Cliente", opacity = 1 )# Mostrar el mapamapa_leaflet```:::> Conclusiones:>> - Parece que los camiones no mezclan productos del cliente 20 y 70.>> - Tiempos muertos, identificar trayectorias por distancia.>> - Errores de carga cuando hay localidades consecutivas.>> Grafico de trayectoria en orden para cada cliente. Identificar puntos que demuestran rutas poco eficientes. Cruces de lineas, tiempo perdido.>> Graficar tiempo entre entregas por hora del día. (Tiempo muerto)>> Ampliar u organizar nuevos centros de distribución. (En base a los datos de estos dos clientes), Segmentación de entregas (global y por cliente) para identificar puntos donde sería ideal. (Centro de zona, cruce de zonas, cluster con más o menor actividad)## Intentos de identificar transportistas.A priori y para facilitar el análisis podríamos suponer que las entregas las realizan dos camiones. Uno para cada cliente. Podemos validar que esto es incorrecto al estudiar las entregas de un único cliente.```{r, warning=FALSE, echo=FALSE}# Filtrar las entregas para la fecha seleccionada y ordenarlas por horaentregas_fecha <- data %>% filter(as.Date(fin_visita) == fecha_seleccionada, cliente == 20) %>% arrange(fin_visita) %>% slice(1:n_entregas) %>% mutate( orden = row_number(), # Agregar el orden de entrega tiempo_transcurrido = c(NA, diff(fin_visita) / 60) # Calcular minutos entre entregas )# Crear etiquetas de tiempo entre entregas para colocarlas en las líneasetiquetas_lineas <- entregas_fecha %>% filter(!is.na(tiempo_transcurrido)) %>% # Excluir la primera entrega (sin tiempo anterior) mutate( lat_medio = (latitud + lag(latitud)) / 2, lng_medio = (longitud + lag(longitud)) / 2, etiqueta_tiempo = paste0(round(tiempo_transcurrido, 2), " min") ) %>% na.omit() # Remover filas con NA (la primera fila)# Crear el mapa interactivo con Leafletmapa_leaflet <- leaflet(data = entregas_fecha) %>% addTiles() %>% # Agregar puntos con colores basados en el cliente y etiquetas personalizadas addCircleMarkers( ~longitud, ~latitud, radius = 8, color = "red", label = ~as.character(orden), # Mostrar el número de orden como etiqueta labelOptions = labelOptions( noHide = TRUE, direction = "center", textOnly = TRUE, style = list( "font-size" = "13px", "font-weight" = "bold", "color" = "white", "border-radius" = "100%", "padding" = "12px" ) ), # Mostrar la hora exacta y tiempo transcurrido como popup popup = ~paste( "Hora de Entrega:", format(fin_visita, "%H:%M:%S"), "<br>Cliente:", cliente, "<br>Tiempo desde la última entrega:", ifelse(is.na(tiempo_transcurrido), "N/A", paste(round(tiempo_transcurrido, 2), "min")) ) ) %>% # Agregar líneas que conecten las entregas en orden addPolylines( lng = ~longitud, lat = ~latitud, data = entregas_fecha, color = "blue", weight = 2 ) %>% # Agregar una leyenda para los clientes addLegend( "bottomright", pal = paleta_colores, values = ~cliente, title = "Cliente", opacity = 1 )# Mostrar el mapamapa_leaflet```Vemos que entre la entrega 18 y 19 pasaron menos de 3minHay algunos casos donde podemos identificar camiones distintos:- Grandes distancias en poco tiempo.- Cruces de rutas.Los cruces de trayectorias podrían indicar rutas ineficientes de los repartidores o que se estan siguiendo varios repartidores en la misma ruta. Podemos verlo de forma clara en el siguiente gráfico:```{r echo=FALSE}# Filtrar las entregas para la fecha seleccionada y ordenarlas por horaentregas_fecha <- data %>% filter(as.Date(fin_visita) == fecha_seleccionada, cliente == 70) %>% arrange(fin_visita) %>% slice(1:n_entregas) %>% mutate( orden = row_number(), # Agregar el orden de entrega tiempo_transcurrido = c(NA, diff(fin_visita) / 60) # Calcular minutos entre entregas )# Crear etiquetas de tiempo entre entregas para colocarlas en las líneasetiquetas_lineas <- entregas_fecha %>% filter(!is.na(tiempo_transcurrido)) %>% # Excluir la primera entrega (sin tiempo anterior) mutate( lat_medio = (latitud + lag(latitud)) / 2, lng_medio = (longitud + lag(longitud)) / 2, etiqueta_tiempo = paste0(round(tiempo_transcurrido, 2), " min") ) %>% na.omit() # Remover filas con NA (la primera fila)# Crear el mapa interactivo con Leafletmapa_leaflet <- leaflet(data = entregas_fecha) %>% addTiles() %>% # Agregar puntos con colores basados en el cliente y etiquetas personalizadas addCircleMarkers( ~longitud, ~latitud, radius = 8, color = "blue", label = ~as.character(orden), # Mostrar el número de orden como etiqueta labelOptions = labelOptions( noHide = TRUE, direction = "center", textOnly = TRUE, style = list( "font-size" = "13px", "font-weight" = "bold", "color" = "white", "border-radius" = "100%", "padding" = "12px" ) ), # Mostrar la hora exacta y tiempo transcurrido como popup popup = ~paste( "Hora de Entrega:", format(fin_visita, "%H:%M:%S"), "<br>Cliente:", cliente, "<br>Tiempo desde la última entrega:", ifelse(is.na(tiempo_transcurrido), "N/A", paste(round(tiempo_transcurrido, 2), "min")) ) ) %>% # Agregar líneas que conecten las entregas en orden addPolylines( lng = ~longitud, lat = ~latitud, data = entregas_fecha, color = "blue", weight = 2 ) %>% # Agregar una leyenda para los clientes addLegend( "bottomright", pal = paleta_colores, values = ~cliente, title = "Cliente", opacity = 1 )# Mostrar el mapamapa_leaflet```Habiendo identificado estas caracteristicas podríamos utilizar un modelo de agrupamiento para estimar distintos repartidores y sus trayectorias.Esto tiene un gran valor para:1. Identificar rutas ineficientes.2. Estimar un tiempo de duración de cada entrega (Y rellenar vacios)3. Identificar patrones en los errores de carga de datos (Duración de entregas)Para la aplicación practica de estos analisis sería vital tener acceso real a los camiones o repartidores responsables de cada entrega pero para el bien de este analisis intentamos aproximar lo más posible a estos datos utilizando modelos estadisticos. Esperamos sirva como prueba de concepto para evaluar el potencial de recolectar estos datos.Esto nos presenta un problema que podría solucionarse con coloración de grafos.**Construir el grafo de conflictos:**- **Nodos:** Cada nodo representa una entrega individual con su respectiva latitud, longitud, fecha y hora.- **Aristas (conflictos):** Se dibuja una arista entre dos entregas si es imposible que hayan sido realizadas por el mismo repartidor debido a restricciones de tiempo y distancia. - **Criterio de conflicto:** Para dos entregas AAA y BBB, se calcula el tiempo mínimo necesario para que un repartidor viaje desde la ubicación de AAA a BBB considerando una velocidad razonable (por ejemplo, la velocidad promedio de un vehículo en esa área). - Si el tiempo transcurrido entre la hora de entrega de AAA y BBB es menor que el tiempo mínimo de viaje calculado, entonces se establece un conflicto entre AAA y BBB.**Algoritmos sugeridos:**- **Algoritmo Greedy de coloreo:** Un enfoque sencillo que asigna colores a los nodos de forma secuencial, utilizando el menor número de colores posible en cada paso.- **Heurísticas avanzadas:** Si el grafo es grande y complejo, pueden emplearse algoritmos como DSATUR o técnicas metaheurísticas (algoritmos genéticos, recocido simulado) para aproximar una solución cercana al mínimo número de colores.Algunos resultados obtenidos utilizando el algoritmo Greedy de coloreo con python:Si bien funciona correctamente para alrededor de 20 entregas a medida que agregamos datos se dificulta el algoritmo y considerando las incosistencias de los datos y errores de carga no sería adecuado estimar usando este algoritmo en su estado actual.Algunas consideraciones.- Toma en cuenta una velocidad promedio de los camiones arbitrairaSi bien podría ser interesante utilizar un algoritmo para resolver o estimar el problema. Evidentmente la mejor solución es tomar los datos de UNIGIS o un software automatico. De todas formas no se podrá extraer valor real con datos de tiempo erroneos.Esta estimación nos presenta algunas preguntas sobre factores de las entregas.1. Rutas optimizadas. Gracias al análisis podemos ver que las rutas estan optimizadas pero parecen estar diseñadas para transportistas individuales y no para la flota como grupo. Optimizar las rutas de entregas para toda la flota como un ente es distinto a diseñar la ruta para cada camion entre dos puntos.# Conclusiones# Apéndice<https://www.youtube.com/watch?v=V8eXoIFcMgM>Incluir enlace interno para versión del trabajo con el codigo y comentarios.Incluir el enlace al repositorio de GithubReferencias de estudio sobre Lastmile logistics, ETL in logistics y clustering geoespacial DBSCAN, H3 oreilly